// Copyright (C) 2008 Victor Marzo
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library.  Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
// 
// As a special exception, the copyright holders of this library give you
// permission to link this library with independent modules to produce an
// executable, regardless of the license terms of these independent
// modules, and to copy and distribute the resulting executable under
// terms of your choice, provided that you also meet, for each linked
// independent module, the terms and conditions of the license of that
// module.  An independent module is a module which is not derived from
// or based on this library.  If you modify this library, you may extend
// this exception to your version of the library, but you are not
// obligated to do so.  If you do not wish to do so, delete this
// exception statement from your version.

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace NForth
{
	public class Compiler
	{
		private static AssemblyBuilder assbuilder;
        private static ModuleBuilder module_builder;
        private static int method_id = 0;
        private static TypeBuilder type_builder;
        private static MethodBuilder method_builder;
        private static string name = "";

        public static void Init()
		{
			AppDomain appdomain = AppDomain.CurrentDomain;
			AssemblyName assembly = new AssemblyName("NForthAssembly");
			assbuilder = appdomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run);
		}

        public static void BeginMethod(string n)
		{
            name = n;

			string id = (++method_id).ToString();
			module_builder = assbuilder.DefineDynamicModule("Module" + id);
			type_builder = module_builder.DefineType("MethodType" + id, TypeAttributes.Public);
			method_builder = type_builder.DefineMethod(name, MethodAttributes.Public | MethodAttributes.Static, null, null);
		}

        public static MethodInfo EndMethod()
		{
			type_builder.CreateType();
			module_builder.CreateGlobalFunctions();
			
			string id = method_id.ToString();
			Type type = module_builder.GetType("MethodType" + id);
			MethodInfo method = type.GetMethod(name);
						
			module_builder = null;
			type_builder = null;
			method_builder = null;
			
			return method;
		}

        public static void Emit(OpCode opcode)
        {
            method_builder.GetILGenerator().Emit(opcode);
        }

        public static void EmitCall(MethodInfo method)
        {
            if (method.IsVirtual)
                method_builder.GetILGenerator().Emit(OpCodes.Callvirt, method);
            else
                method_builder.GetILGenerator().Emit(OpCodes.Call, method);
        }

        public static void EmitType(OpCode opcode, Type type)
        {
            method_builder.GetILGenerator().Emit(opcode, type);
        }

        public static void EmitField(OpCode opcode, FieldInfo field)
        {
            method_builder.GetILGenerator().Emit(opcode, field);
        }

        public static void EmitLiteral(object i)
        {
            Type t = i.GetType();
            if (t.Name == "RuntimeType")
                t = t.BaseType;

            if (t == typeof(string))
                method_builder.GetILGenerator().Emit(OpCodes.Ldstr, (string)i);
            else if (t == typeof(Type))
                method_builder.GetILGenerator().Emit(OpCodes.Ldtoken, (Type)i);
            else if (t == typeof(int))
            	method_builder.GetILGenerator().Emit(OpCodes.Ldc_I4, (int)i);
            else if (t.Name == "RuntimeMethodInfo")
            {
                MethodInfo method_info = (MethodInfo)i;
                EmitType(OpCodes.Ldtoken, method_info.DeclaringType);
                EmitCall(typeof(Type).GetMethod("GetTypeFromHandle"));
                EmitLiteral(method_info.Name);
                EmitCall(Dictionary.FindMethod("Type.GetMethod`String"));
            }
            else
                try
                {
                    FieldInfo field_info = (FieldInfo)i;
                    EmitType(OpCodes.Ldtoken, field_info.DeclaringType);
                    EmitCall(typeof(Type).GetMethod("GetTypeFromHandle"));
                    EmitLiteral(field_info.Name);
                    EmitCall(Dictionary.FindMethod("Type.GetField`String"));
                }
                catch (Exception)
                {
                    method_builder.GetILGenerator().Emit(OpCodes.Ldnull);
                }
        }
        
        public static void DeclareLocal(Type t)
        {
        	method_builder.GetILGenerator().DeclareLocal(t);
        }

        public static Label DefineLabel()
        {
            return method_builder.GetILGenerator().DefineLabel();
        }

        public static void MarkLabel(Label l)
        {
            method_builder.GetILGenerator().MarkLabel(l);
        }

        public static void EmitLabel(OpCode opcode, Label label)
        {
            method_builder.GetILGenerator().Emit(opcode, label);
        }
    }
}
